Keygen Tutorial 1 Yes123 '99 ---[ WARNING ]-------------------------------------------------------------------------------- This Tutorial is for education purpose only. I wrote it to allow you to understand how are coded some protections schemes in software. I didn't make it to allow you to use the target program without paying the author. If you plan to use these programs regularly, please remeber to send your $ to the authors, don't be a outlaw, and over all, don't be a LAMER !!! ---[ INTRO ]---------------------------------------------------------------------------------- target: A Day in the Life v1.0 where : http://www.cartoonlogic.com Tools : SoftIce for Win9x v3.24 W32Dasm v8.9 Programming Language (C, Pascal, asm, anyone you want, I'll use our old C) This is my first tutorial in english. I hope my bad spelling won't make this text too much hard to understand for you. :) I'll try to teach you how to make a key generator for a program. The way i teach will be based more on reverse enginerring (instead of only cracking the program, we try to fully understand the whole key-generating algorithm), some newbies maybe having problem of reading this. I'll assume you know the following: - basic use of SoftIce - asm instructions (at least the ones used for cracking) ---[ TUTORIAL ]------------------------------------------------------------------------------- At first, launch SoftIce (assuming you know the basics, and how to setup this Numega's nice tool). Then launch our target, ADITL1.0.exe! Go to the help menu and select the register. Well, we'll try to find how this protection is running. Let's enter anything as a name and random digits as a serial. Don't click the OK button yet, but hit CTRL & D to bring up SoftICe. We'll define a breakpoint, using the classical BPX hmemcpy(it works for most program.) Hit CTRL-D again to go back to the program. Click OK and... SoftICe pop up. Keep pressing F12 until you get back to the traget program code. Trace a few lines down you will be in: :004848A7 E8D4B1F9FF call 0041FA80 ;start here :004848AC 8B45F8 mov eax, dword ptr [ebp-08] ;eax=pointer to name :004848AF E8C4F3F7FF call 00403C78 ;return eax=length of name :004848B4 8BF0 mov esi, eax ;esi=length of name :004848B6 85F6 test esi, esi ;test length :004848B8 7E28 jle 004848E2 ;if no name enterthen jump :004848BA B901000000 mov ecx, 00000001 ;counter=1;ebx=1 firstly * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004848E0(C) *first important loop* ====================== :004848BF 8B45F8 mov eax, dword ptr [ebp-08] ;move name to eax :004848C2 0FB67C08FF movzx edi, byte ptr [eax+ecx-01];edi=name[ecx] :004848C7 0FAFFB imul edi, ebx ;edi=edi*ebx :004848CA 83FF12 cmp edi, 00000012 ;compare edi with 0x12 :004848CD 7C0F jl 004848DE ;jump if less than :004848CF 8B45F8 mov eax, dword ptr [ebp-08] ;move name to eax :004848D2 8BC7 mov eax, edi ;eax=edi :004848D4 BB11000000 mov ebx, 00000011 ;ebx=11 :004848D9 99 cdq ;expand eax :004848DA F7FB idiv ebx ;eax=eax/11 :004848DC 8BD8 mov ebx, eax ;ebx=eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004848CD(C) | :004848DE 41 inc ecx :004848DF 4E dec esi :004848E0 75DD jne 004848BF ;if unfinish loop back The above code is the first important loop of the key-genereating loop.It is basicaaly a very simple coding and can be summurized as follow: code=name[ecx]*code/17 Ecx act as a counter, it count the current byte of name in increasing order, and the ebx is the code we want. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004848B8(C) | :004848E2 8B45F8 mov eax, dword ptr [ebp-08] ;move name to eax :004848E5 E88EF3F7FF call 00403C78 ;calculate length of eax :004848EA 8BC8 mov ecx, eax :004848EC 83F901 cmp ecx, 00000001 :004848EF 7C30 jl 00484921 ;jump if no name enter * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0048491F(C) *second important loop* ======================= :004848F1 8B45F8 mov eax, dword ptr [ebp-08] ;move name to eax :004848F4 0FB67408FF movzx esi, byte ptr [eax+ecx-01];move name[ecx] to esi :004848F9 8BC1 mov eax, ecx ;ecx is in decresing order :004848FB 99 cdq ;clear edx :004848FC F7FE idiv esi ;eax = count / name[ecx] :004848FE 85D2 test edx, edx ;test if any number remain :00484900 740D je 0048490F ;if no then jump :00484902 8B45F8 mov eax, dword ptr [ebp-08] ;eax=name :00484905 83EE0B sub esi, 0000000B ;name[ecx]=name[ecx]-0B :00484908 0FAFF3 imul esi, ebx ;code=esi*code :0048490B 8BDE mov ebx, esi ;ebx=code :0048490D EB0D jmp 0048491C ;unconditional jump * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00484900(C) | :0048490F 8B45F8 mov eax, dword ptr [ebp-08] ;eax=name :00484912 83C611 add esi, 00000011 ;name[0]=name[0]+0x11 :00484915 8BC3 mov eax, ebx ;eax=code :00484917 99 cdq ;clear edx :00484918 F7FE idiv esi ;eax=eax/esi :0048491A 8BD8 mov ebx, eax ;ebx=code * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0048490D(U) | :0048491C 49 dec ecx :0048491D 85C9 test ecx, ecx :0048491F 75D0 jne 004848F1 ;loop if unfinish The second important loop is also quite simple, we can neglect line 48490F to 48491A because it will never reach when is came to last byte, it will pass throught 48491F and never have change to loop back. So the testing algorithem from 4848FB to 484900 actually is rubbish code. This might be a bug or something that make to confuse us. The above coding can be summerized as: code=code*(name[ecx]-0x0b) ;code in this case is our serial calculated Ecx is a counter in decreasing order, it start it the last byte of our name. Ebx is the code we want. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004848EF(C) | :00484921 8B45F8 mov eax, dword ptr [ebp-08] :00484924 E84FF3F7FF call 00403C78 :00484929 50 push eax :0048492A 8BC3 mov eax, ebx :0048492C 5A pop edx ;edx=length of name :0048492D 2BC2 sub eax, edx ;code=code-length :0048492F B907000000 mov ecx, 00000007 ;ecx=0x07 :00484934 99 cdq :00484935 F7F9 idiv ecx ;code=code/7 :00484937 05920F0000 add eax, 00000F92 ;code=code+0x0f92 :0048493C 8BD8 mov ebx, eax ;ebx=code Until here our code=((code-length of our name)/7)+0F92 :0048493E 8D55F4 lea edx, dword ptr [ebp-0C] ;edx=location [final reg key] :00484941 8BC3 mov eax, ebx ;eax=code :00484943 E8042BF8FF call 0040744C ;This call convert the int format :00484948 8D45F0 lea eax, dword ptr [ebp-10] ;of the code into character, :0048494B 8B55F8 mov edx, dword ptr [ebp-08] ;including it's sign also :0048494E 8A12 mov dl, byte ptr [edx] The call at 484943 actually convert our code into string form including sign also. :00484950 E84BF2F7FF call 00403BA0 This call search for the char need to be insert in first byte of our serial, it return the in [ebp-10]. In this case is search for the first byte of our name as result. :00484955 8B45F0 mov eax, dword ptr [ebp-10] ;eax=target of insertion :00484958 8D55F4 lea edx, dword ptr [ebp-0C] ;edx=target for insertion :0048495B B901000000 mov ecx, 00000001 ;ecx=location :00484960 E89FF5F7FF call 00403F04 ;This call insert first char :00484965 8B55F8 mov edx, dword ptr [ebp-08] ;of name into first location :00484968 B8744A4800 mov eax, 00484A74 ;of code The call 00403F04 actually is a call to insert certain character into the string. It need EAX for what we want to insert, in above case it need to insert the first char of name. EDX is the address for the target to insert, in above case edx=address of our serial. ECX is the location for insertion, in above case it is insert at first byte. :0048496D E8EEF5F7FF call 00403F60 ;This call for any space in our :00484972 8B55F8 mov edx, dword ptr [ebp-08] ;name :00484975 8A1402 mov dl, byte ptr [edx+eax] :00484978 8D45F0 lea eax, dword ptr [ebp-10] :0048497B E820F2F7FF call 00403BA0 ;This call return eax=the char :00484980 8B45F0 mov eax, dword ptr [ebp-10] ;should placed in last byte of :00484983 50 push eax ;code :00484984 8B45F4 mov eax, dword ptr [ebp-0C] :00484987 E8ECF2F7FF call 00403C78 ;Calculate last byte location :0048498C 8BC8 mov ecx, eax :0048498E 41 inc ecx :0048498F 8D55F4 lea edx, dword ptr [ebp-0C] :00484992 58 pop eax :00484993 E86CF5F7FF call 00403F04 ;This call insert eax from call :00484998 8B45F4 mov eax, dword ptr [ebp-0C] ;00403BA0 into last byte of code The above code search for any space(20) in our name, if it does then the take the next byte after the FIRST space and put it into last byte of our code. If it doesn't found any space, then it will put first byte of our name into last byte of our code. Eg: Name=abc code=a15335672a Name=abc def code=a87672812d Name=abc 123 456 code=a-15942455d :0048499B E8D8F2F7FF call 00403C78 :004849A0 8BC8 mov ecx, eax :004849A2 D1F9 sar ecx, 1 ;ecx=length(name)>>1 :004849A4 7903 jns 004849A9 ;jump if ecx>0 :004849A6 83D100 adc ecx, 00000000 The above code calculate the length of our name, ecx = our length of name shift right by one. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004849A4(C) | :004849A9 8D55F4 lea edx, dword ptr [ebp-0C] * Possible StringData Ref from Code Obj ->"ClaDiTL" | :004849AC B8804A4800 mov eax, 00484A80 :004849B1 E84EF5F7FF call 00403F04 ;This call insert "ClaDiTL" into :004849B6 8D55EC lea edx, dword ptr [ebp-14] ;ecx location of code :004849B9 8B45FC mov eax, dword ptr [ebp-04] :004849BC 8B80EC010000 mov eax, dword ptr [eax+000001EC] :004849C2 E8B9B0F9FF call 0041FA80 The above code inserts words'ClaDiTL" into our code, it insert in the location ecx, and ecx is our name length right shift by one. Eg:With name=yes123 before insertion, code=y-85638181y after insertion, code=y-85ClaDiTL638181y :004849C7 8B55EC mov edx, dword ptr [ebp-14] ;edx=our false key :004849CA 8B45F4 mov eax, dword ptr [ebp-0C] ;eax=real key :004849CD E8B6F3F7FF call 00403D88 ;test for both key :004849D2 7530 jne 00484A04 ;jump if not equal The this part, it compare our fake key with the real key generated. So we can type 'd eax' after line 4849CA to sea for the real key. :004849D4 8D55EC lea edx, dword ptr [ebp-14] :004849D7 8B45FC mov eax, dword ptr [ebp-04] :004849DA 8B80EC010000 mov eax, dword ptr [eax+000001EC] :004849E0 E89BB0F9FF call 0041FA80 :004849E5 8B55EC mov edx, dword ptr [ebp-14] * Possible StringData Ref from Code Obj ->"ClaDiTL" | :004849E8 B8804A4800 mov eax, 00484A80 :004849ED E86EF5F7FF call 00403F60 :004849F2 85C0 test eax, eax :004849F4 7E0E jle 00484A04 :004849F6 A114F74800 mov eax, dword ptr [0048F714] :004849FB 8B00 mov eax, dword ptr [eax] :004849FD E8BA7B0000 call 0048C5BC :00484A02 EB37 jmp 00484A3B The above code store our registered data into registry. * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:004849D2(C), :004849F4(C) | :00484A04 6A00 push 00000000 :00484A06 668B0D884A4800 mov cx, word ptr [00484A88] :00484A0D B201 mov dl, 01 * Possible StringData Ref from Code Obj ->"Invalid registration code!" | :00484A0F B8944A4800 mov eax, 00484A94 ;Change this code to 8B45F49090 When we trace to here, there is also a fun place to make change, we can change the : 00484A0F B8944A4800 mov eax, 00484A94 into: 00484A0F 8B45F4 mov eax, dword ptr [ebp-0C] 00484A12 90 nop 00484A13 90 nop So that instead of display out the "Invalid registration code!", it will display out our real key if we enter wrong key. This is beacuse from line 4849CA, we know the real key is store at [ebp-0C]. You can try and make a patch for for it. This technique is so call 'Magic Window'. :00484A14 E8DB7CFBFF call 0043C6F4 :00484A19 8B45FC mov eax, dword ptr [ebp-04] :00484A1C 8B80E0010000 mov eax, dword ptr [eax+000001E0] :00484A22 8B10 mov edx, dword ptr [eax] :00484A24 FF9294000000 call dword ptr [edx+00000094] :00484A2A 8B45FC mov eax, dword ptr [ebp-04] :00484A2D 8B80EC010000 mov eax, dword ptr [eax+000001EC] :00484A33 8B10 mov edx, dword ptr [eax] :00484A35 FF9294000000 call dword ptr [edx+00000094] * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00484A02(U) | :00484A3B 33C0 xor eax, eax :00484A3D 5A pop edx :00484A3E 59 pop ecx :00484A3F 59 pop ecx :00484A40 648910 mov dword ptr fs:[eax], edx * Possible StringData Ref from Code Obj ->"_^[" | :00484A43 68654A4800 push 00484A65 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00484A63(U) | :00484A48 8D45EC lea eax, dword ptr [ebp-14] :00484A4B E8ACEFF7FF call 004039FC :00484A50 8D45F0 lea eax, dword ptr [ebp-10] :00484A53 BA03000000 mov edx, 00000003 :00484A58 E8C3EFF7FF call 00403A20 :00484A5D C3 ret ---[ OVERALL CONCLUSION OF KEY GENERATING PROCESS ]-------------------------------------------- 1.code=(code*name[count1])/0x11; //count1=counter from 0 to length of name 2.code=code*(name[count2]-0x0b); //count2=counter from length of name to 0 3.code=((code-count1)/7)+0xf92; //count1=length of name 4.convert code into string form. 5.insert suitable character into the head and tail of the code. 6.insert 'ClaDiTL' into suitable place of the code ---[ C-LANGUAGE CODE ]------------------------------------------------------------------------- #include#include #include int main(void) { long code=1; int count1,count2,count3; char name[25],reg[15]; clrscr(); textcolor(14); cprintf(" __,__\r\n"); cprintf(" / \\\r\n" ); cprintf(" vvvvvvv /|__/|\r\n"); cprintf(" I /O,O |\r\n"); cprintf(" I /_____ | /|/|\r\n"); cprintf(" J|/^ ^ ^ \\ | /00 | _//|\r\n"); cprintf(" |^ ^ ^ ^ |W| |/^^\\ | /oo |\r\n"); cprintf(" \\m___m__|_| \\m_m_| \\mm_|\r\n"); textcolor(10); cprintf("======================================="); textcolor(11); cprintf("\r\nKeyGenerator for A Day in the Life v1.0"); textcolor(10); cprintf("\r\n======================================="); printf("\nCracked by "); textcolor(14); cprintf("%c%c%c",0x10,0x10,0x10); textcolor(12); cprintf("Yes123"); textcolor(14); cprintf("%c%c%c",0x11,0x11,0x11); printf(" - February 1999"); printf("\n\nEnter register name = "); scanf("%[^\n]",name); for(count1=0;name[count1]>0;count1++) //first loop code=(code*name[count1])/0x11; for(count2=count1-1;count2>=0;count2--) //second loop code=code*(name[count2]-0x0b); code=((code-count1)/7)+0xf92; ltoa(code,reg,10); //convert long to string printf("Your registration key = %c",name[0]); for(count2=0;reg[count2]>0;count2++); count2=(count2+2)>>1; for(count1=0;count1<(count2-2);count1++) printf("%c",reg[count1]); printf("ClaDiTL"); for(;reg[count1]>0;count1++) printf("%c",reg[count1]); for(count1=0;name[count1]>0;count1++){ if ((name[count1]==0x20)&&(name[count1+1]>0)){ printf("%c",name[count1+1]); return; } } printf("%c",name[0]); return ; } ---[ LAST ]----------------------------------------------------------------------------------- I know my english expressing ability is weak, so in many place i know what's happening but i cann't express well and explain to you clearly. I hope you can really try it out then you can understand more out of it. This is my first keygen tutorial and i hope you'll like it. You can also try to trace into the call of how they insert words into string, call of how they calculate the strings number, call of how they compare two strings, call of how they search for specific char in a string... Try to understand their coding, it is quite interesting and useful also if we met another similar call next time. Thanks for reading my first keygen tutorial. ---[ THAT'S ALL FOLKS ]----------------------------------------------------------------------- Yes123 '99 ==========